/**
 * \file            bsp_key.c
 * \brief           universal key driver
 */

/*
 * Copyright (c) 2022 Dino Haw
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file is part of mOTA - The Over-The-Air technology component for MCU.
 *
 * Author:          Dino Haw <347341799@qq.com>
 * Version:         v1.0.0
 */

/* Includes ------------------------------------------------------------------*/
#include "bsp_key.h"

/* Private variables ---------------------------------------------------------*/
#define EVENT_CALLBACK(event)                                 \
    do                                                        \
    {                                                         \
        if (key_index->Callback[event])                       \
        {                                                     \
            key_index->Callback[event](key_index->id, event); \
        }                                                     \
    } while (0)

/* Private function prototypes -----------------------------------------------*/
static struct BSP_KEY *_key_head;

/* Exported functions ---------------------------------------------------------*/
/**
 * @brief  按键对象初始化
 * @note
 * @param[in]  key: 按键句柄
 * @param[in]  id: 按键 ID，用于识别不同的按键
 * @param[in]  KEY_GetState: 按键状态获取回调函数
 * @param[in]  press_level: 按键按下时的电平状态
 * @param[in]  press_time: 设置按键长按的间隔时间
 * @retval 执行结果
 */
#if (KEY_USER_DEFINE_PRESS_TIME)
int8_t BSP_Key_Init(struct BSP_KEY *key, uint8_t id, uint8_t (*KEY_GetState)(void), KEY_LEVEL press_level, uint16_t press_time)
#else
int8_t BSP_Key_Init(struct BSP_KEY *key, uint8_t id, uint8_t (*KEY_GetState)(void), KEY_LEVEL press_level)
#endif
{
    memset(key, 0, sizeof(struct BSP_KEY));

    key->id = id;
    key->press_level = press_level;
    key->continuous_value = 0xFF;
    key->GetState = KEY_GetState;
#if (KEY_USER_DEFINE_PRESS_TIME)
    key->press_time = press_time;
#endif

    return 0;
}

/**
 * @brief  按键启用
 * @note   每个按键初始化并注册好相应的按键事件回调函数后，必须调用本函数才能使用
 * @param[in]  key: 按键句柄
 * @retval 执行结果
 */
int8_t BSP_Key_Start(struct BSP_KEY *key)
{
    struct BSP_KEY *key_index = _key_head;

    while (key_index)
    {
        if (key_index == key)
        {
            return 1;
        }
        key_index = key_index->next;
    }

    key->next = _key_head;
    _key_head = key;

    return 0;
}

/**
 * @brief  按键停用
 * @note   当按键不再需要使用时，便可调用本函数
 * @param[in]  key:按键句柄
 * @retval None
 */
void BSP_Key_Stop(struct BSP_KEY *key)
{
    struct BSP_KEY *key_now = _key_head;
    struct BSP_KEY *key_last;

    for (; key_now; key_now = key_now->next)
    {
        if (key_now == key)
        {
            if (key == _key_head)
            {
                _key_head = key->next;
                return;
            }

            if (key->next)
            {
                key_last->next = key->next;
            }
            else
            {
                key_last->next = NULL;
            }
        }
        key_last = key_now;
    }
}

/**
 * @brief  注册按键事件触发时的回调函数
 * @note   若需要使用按键，则至少应该注册一个按键事件的回调函数
 * @param[in]  key: 按键句柄
 * @param[in]  event: 按键事件
 * @param[in]  callback: 按键事件回调函数
 * @retval None
 */
void BSP_Key_Register(struct BSP_KEY *key, KEY_EVENT event, Key_EventCallback callback)
{
    key->Callback[event] = callback;
}

/**
 * @brief  按键业务处理函数
 * @note   若需要使用按键的任何功能，都需要调用本函数，并周期性的循环执行
 * @param[in]  ms: 告知本函数每隔多少毫秒被执行1次
 * @retval None
 */
void BSP_Key_Handler(uint8_t ms)
{
    // uint8_t i = 0;
    uint8_t times = 0xFF;
    struct BSP_KEY *key_index = _key_head;

    // for (; i < (8 - KEY_CONSECUTIVE_READ_TIME); i++)
    //     times >>= 1;

    times >>= (8 - KEY_CONSECUTIVE_READ_TIME);

    for (; key_index; key_index = key_index->next)
    {
        /* 获取按键电平值 */
        key_index->continuous_value = (key_index->continuous_value << 1) | key_index->GetState();

        /* 获取按键按下或弹起状态 */
        if ((key_index->continuous_value & times) == 0x00)
        {
            if (key_index->press_level == KEY_PRESS_LOW)
            {
                key_index->key_state = KEY_DOWN;
            }
            else
            {
                key_index->key_state = KEY_UP;
            }
        }
        else if ((key_index->continuous_value & times) == times)
        {
            if (key_index->press_level == KEY_PRESS_HIGH)
            {
                key_index->key_state = KEY_DOWN;
            }
            else
            {
                key_index->key_state = KEY_UP;
            }
        }

        /* 按键状态机 */
        switch (key_index->state)
        {
        case 0:
        {
            if (key_index->key_state == KEY_DOWN) /* 首次按下 */
            {
                key_index->event = KEY_PRESS;
                EVENT_CALLBACK(KEY_PRESS);
                key_index->state = 1;
            }
            break;
        }
        case 1:
        {
            if (key_index->key_state == KEY_UP) /* 按下后放开 */
            {
                key_index->tick = 0;
#if (KEY_CLICK_EVENT_WHEN_DBLCLICK)
                key_index->event = KEY_CLICK;
                EVENT_CALLBACK(KEY_CLICK);
#endif
                key_index->state = 2;
            }
            else if (key_index->key_state == KEY_DOWN) /* 还处于第一次按下的状态 */
            {
                key_index->tick += ms;

#if (KEY_USER_DEFINE_PRESS_TIME)
                if (key_index->tick >= key_index->press_time) /* 第一次按下时间已超过长按触发时间 */
#else
                if (key_index->tick >= KEY_LONG_PRESS_TIME) /* 超时未按下 */
#endif
                {
                    key_index->tick = 0;
                    key_index->event = KEY_LONG_PRESS;
                    EVENT_CALLBACK(KEY_LONG_PRESS);
                    key_index->state = 4;
                }
            }
            break;
        }
        case 2: /* 检测是否双击 */
        {
            if (key_index->key_state == KEY_DOWN) /* 单击后再次按下 */
            {
                if (KEY_DBLCLICK_PRESS_VALID) /* 第二次按下立即触发标志 */
                {
                    key_index->event = KEY_DBLCLICK;
                    EVENT_CALLBACK(KEY_DBLCLICK);
                }
                key_index->tick = 0;
                key_index->state = 3;
            }
            else if (key_index->key_state == KEY_UP) /* 未按下 */
            {
                key_index->tick += ms;

                if (key_index->tick >= KEY_DBLCLICK_TIME) /* 单击后超时未再按下，事件结束  */
                {
#if (KEY_CLICK_EVENT_WHEN_DBLCLICK == 0)
                    key_index->event = KEY_CLICK;
                    EVENT_CALLBACK(KEY_CLICK);
#endif
                    key_index->tick = 0;
                    key_index->state = 0;
                    key_index->event = KEY_NONE_EVENT;
                }
            }
            break;
        }
        case 3: /* 双击事件，第二次已按下 */
        {
            if (key_index->key_state == KEY_UP) /* 第二次已放开按键 */
            {
                if (KEY_DBLCLICK_PRESS_VALID == 0) /* 第二次按下放开后才触发标志 */
                {
                    key_index->event = KEY_DBLCLICK;
                    EVENT_CALLBACK(KEY_DBLCLICK);
                }
                key_index->tick = 0;
                key_index->state = 0;
                key_index->event = KEY_NONE_EVENT;
            }
            break;
        }
        case 4: /* 长时间按着（进入此处时长按回调事件已经被执行完毕） */
        {
            if (key_index->key_state == KEY_DOWN) /* 此时依旧被按着 */
            {
                key_index->tick += ms;

#if (KEY_LONG_PRESS_CONTINU_TRIG_TIME)
                /* 需要长按保持触发 */
                if (key_index->tick >= KEY_LONG_PRESS_CONTINU_TRIG_TIME)
                {
                    key_index->tick = 0;
                    key_index->event = KEY_LONG_PRESS_HOLD;
                    EVENT_CALLBACK(KEY_LONG_PRESS_HOLD);
                }
#endif
            }
            else if (key_index->key_state == KEY_UP) /* 用户已放开按键，流程结束 */
            {
                key_index->tick = 0;
                key_index->state = 0;
                key_index->event = KEY_NONE_EVENT;
            }
            break;
        }
        default:
            break;
        }
    }
}

#if (KEY_TEST)
#include "bsp_gpio.h"
#include "bsp_uart.h"

struct BSP_KEY s_key;
struct BSP_KEY c_key;
struct BSP_KEY sc_key;

static uint8_t S_Key_GetState(void)
{
    return BSP_GPIO_Read(s_key.id);
}

static uint8_t C_Key_GetState(void)
{
    return BSP_GPIO_Read(c_key.id);
}

/* 组合按键 */
static uint8_t SC_Key_GetState(void)
{
    if (BSP_GPIO_Read(s_key.id) || BSP_GPIO_Read(c_key.id))
        return GPIO_HIGH;
    else
        return GPIO_LOW;
}

static void Key_PressCallback(uint8_t id, uint8_t event)
{
    BSP_Printf("[ key ] You just press the button[%d], event: %d\r\n\r\n", id, event);
}

void Key_Test(void)
{
    /*    BSP_Printf("size: %d\r\n\r\n", sizeof(func_key)); */

    BSP_Key_Init(&s_key, GET_PIN(B, 8), S_Key_GetState, KEY_PRESS_LOW);
    BSP_Key_Init(&c_key, GET_PIN(B, 9), C_Key_GetState, KEY_PRESS_LOW);
    BSP_Key_Init(&sc_key, 0, SC_Key_GetState, KEY_PRESS_LOW);

    BSP_Key_Register(&s_key, KEY_PRESS, Key_PressCallback);
    BSP_Key_Register(&s_key, KEY_CLICK, Key_PressCallback);
    BSP_Key_Register(&s_key, KEY_DBLCLICK, Key_PressCallback);
    BSP_Key_Register(&s_key, KEY_LONG_PRESS, Key_PressCallback);
    BSP_Key_Register(&c_key, KEY_PRESS, Key_PressCallback);
    BSP_Key_Register(&c_key, KEY_CLICK, Key_PressCallback);
    BSP_Key_Register(&c_key, KEY_DBLCLICK, Key_PressCallback);
    BSP_Key_Register(&c_key, KEY_LONG_PRESS, Key_PressCallback);

    BSP_Key_Register(&sc_key, KEY_CLICK, Key_PressCallback);
    BSP_Key_Register(&sc_key, KEY_DBLCLICK, Key_PressCallback);

    BSP_Key_Start(&s_key);
    BSP_Key_Start(&c_key);
    BSP_Key_Start(&sc_key);
}
#endif
